<?php
/**
 * +--------------------------------------------------------------
 * | eLuoPHP
 * +--------------------------------------------------------------
 * | Copyright (c) 2016-2018 http://qxwoo.cn All rights reserved.
 * +--------------------------------------------------------------
 * | y139w [y139w@163.com]
 * +--------------------------------------------------------------
 * | 自动加载类文件
 * +--------------------------------------------------------------
 */

namespace luo;

class Loader{

    private static $_instance;

    /**
     * 一个关联数组，其中键是命名空间前缀
     * 键所对应的是命名空间所对应的目录
     * @var array
     */
    protected $prefixes = [];

    /**
     * 静态方法调用类
     * @return Loader
     */
    public static function instance(){

        if( !(self::$_instance instanceof self) ){
            self::$_instance = (new self);
        }
        return self::$_instance;

    }

    /**
     * 魔术方法修改私有属性
     * @param string $name 属性名
     * @param string $value 属性值
     */
    public function __set($name, $value){
        if(isset($this->$name)){
            $this->$name = $value;
        }
    }

    /**
     * 注册加载到SPL自动加载队列
     * @return void
     */
    public function register(){
        spl_autoload_register([$this, 'loadClass'], true, true);
    }

    /**
     * 为命名空间前缀添加基本目录
     * @param string $prefix 命名空间前缀
     * @param string $base_dir 一个命名空间中的类文件的目录
     * @param bool $prepend 如果是true，就添加到队列最前，否则添加到队列最后。
     * @return $this
     */
    public function addNamespace($prefix, $base_dir, $prepend = false){
        // 规范命名空间前缀
        $prefix = trim($prefix, '\\') . '\\';

        // 规范目录分隔符
        $base_dir = rtrim($base_dir, DIRECTORY_SEPARATOR) . '/';

        // 初始化命名空间前缀数组
        if (isset($this->prefixes[$prefix]) === false) {
            $this->prefixes[$prefix] = [];
        }

        // 保留命名空间的目录
        if ($prepend) {
            array_unshift($this->prefixes[$prefix], $base_dir);
        } else {
            array_push($this->prefixes[$prefix], $base_dir);
        }
        return $this;

    }

    /**
     * 数组方式为命名空间前缀添加基本目录
     * @param array $array 命名空间前缀数组
     * @return $this
     */
    public function addNamespaceArray($array){
        foreach($array as $prefix=>$basedir){
            $this->addNamespace($prefix, $basedir['dirpath'], $basedir['prepend']);
        }
        return $this;
    }

    /**
     * 用给定类名加载类文件
     * @param string $class 完全适当的类名
     * @return mixed 成功时返回映射的文件名，失败时返回布尔false。
     */
    public function loadClass($class){

        // 当前命名空间前缀
        $prefix = $class;

        // 通过完全适当的类名的命名空间名称向后工作，以找到映射的文件名。
        while (false !== $pos = strrpos($prefix, '\\')) {

            // 保留前缀中的尾随命名空间分隔符。
            $prefix = substr($class, 0, $pos + 1);
            // 剩下的就是相对的类名称。
            $relative_class = substr($class, $pos + 1);

            // 尝试为前缀和相关类加载映射文件。
            $mapped_file = $this->loadMappedFile($prefix, $relative_class);
            if ($mapped_file) {
                return $mapped_file;
            }

            // 删除前缀右边的分隔符
            $prefix = rtrim($prefix, '\\');
        }

        // 找不到映射文件。
        return false;
    }

    /**
     * 为命名空间前缀和相关类加载映射文件。
     * @param string $prefix 命名空间前缀
     * @param string $relative_class 相对的类名。
     * @return mixed 如果没有加载映射文件则为布尔false，否则为映射文件的名称。
     */
    protected function loadMappedFile($prefix, $relative_class){

        // 这个命名空间前缀有目录吗？
        if (isset($this->prefixes[$prefix]) === false) {
            return false;
        }

        // 查看这个命名空间前缀的目录
        foreach ($this->prefixes[$prefix] as $base_dir) {

            // 用基目录替换名称空间前缀，
            // 在相对类名中用目录分隔符替换命名空间分隔符，并附加PHP。
            $file = $base_dir
                . str_replace('\\', '/', $relative_class)
                . '.php';
            // 如果映射文件存在，则需要它。
            if ($this->requireFile($file)) {
                // 是的，我们完成了。
                return $file;
            }
        }

        // 没有找到它
        return false;
    }

    /**
     * 如果存在文件，则需要从文件系统中获取它。
     * @param string $file 需要的文件。
     * @return bool 如果文件存在（true）,如果不存在（false）。
     */
    protected function requireFile($file){

        // 使用相对路径引入文件
        if (file_exists($file)) {
            require $file;
            return true;
        }

        return false;
    }
}
